#ifndef OPENGL_GLSLPROGRAM_H
#define OPENGL_GLSLPROGRAM_H
#ifdef WIN32
#pragma warning( disable : 4290 )
#endif
#include <glad\glad.h>
#include <string>
#include <map>
#include <glm/glm.hpp>
#include <stdexcept>
#include <memory>
namespace ShaderLab {

	enum ShaderType {
	    VERTEX			= GL_VERTEX_SHADER,
	    FRAGMENT		= GL_FRAGMENT_SHADER,
	    GEOMETRY		= GL_GEOMETRY_SHADER,
	    TESS_CONTROL	= GL_TESS_CONTROL_SHADER,
	    TESS_EVALUATION = GL_TESS_EVALUATION_SHADER,
	    COMPUTE			= GL_COMPUTE_SHADER
	};
	enum ShaderDataType {
		FLOAT	= GL_FLOAT,
		FLOAT2	= GL_FLOAT_VEC2,
		FLOAT3  = GL_FLOAT_VEC3,	
		FLOAT4	= GL_FLOAT_VEC4,
		INT		= GL_INT,
		UINT	= GL_UNSIGNED_INT,
		DOUBLE  = GL_DOUBLE,
		BOOL	= GL_BOOL,
		MAT2	= GL_FLOAT_MAT2,
		MAT3	= GL_FLOAT_MAT3,
		MAT4	= GL_FLOAT_MAT4
	};
	enum Usage {
		STATIC_COPY	 = GL_STATIC_COPY,
		STATIC_DRAW	 = GL_STATIC_DRAW,
		STATIC_READ	 = GL_STATIC_READ,
		DYNAMIC_COPY = GL_DYNAMIC_COPY,
		DYNAMIC_DRAW = GL_DYNAMIC_DRAW,
		DYNAMIC_READ = GL_DYNAMIC_READ,
		STREAM_COPY  = GL_STREAM_COPY,
		STREAM_DRAW  = GL_STREAM_DRAW,
		STREAM_READ  = GL_STREAM_READ,
		DYNAMIC_STORAGE_BIT = GL_DYNAMIC_STORAGE_BIT
	};

    class GLSLProgram {
    private:
        GLuint handle;
        bool linked;
        std::map<std::string, int> uniformLocations;

        GLint getUniformLocation(const char* name);
        void detachAndDeleteShaderObjects();
        bool fileExists(const std::string& fileName);
        std::string getExtension(const char* fileName);

    public:
        GLSLProgram();
        ~GLSLProgram();

        GLSLProgram(const GLSLProgram&) = delete;
        GLSLProgram& operator=(const GLSLProgram&) = delete;

        void compileShader(const char* fileName);
        void compileShader(const char* fileName, ShaderType type);
        void compileShader(const std::string& source, ShaderType type, const char* fileName = NULL);

        void link();
        void validate();
        void use();

        int getHandle();
        bool isLinked();

        void bindAttribLocation(GLuint location, const char* name);
        void bindFragDataLocation(GLuint location, const char* name);

        void setUniform(const char* name, float x, float y, float z);
        void setUniform(const char* name, const glm::vec2& v);
        void setUniform(const char* name, const glm::vec3& v);
        void setUniform(const char* name, const glm::vec4& v);
        void setUniform(const char* name, const glm::mat4& m);
        void setUniform(const char* name, const glm::mat3& m);
        void setUniform(const char* name, float val);
        void setUniform(const char* name, int val);
        void setUniform(const char* name, bool val);
        void setUniform(const char* name, GLuint val);

        void findUniformLocations();
        void printActiveUniforms();
        void printActiveUniformBlocks();
        void printActiveAttribs();
        const char* getTypeString(GLenum type);
    };
}
#endif // !COMMON_GLSLPROGRAM_H
